﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace InstantSpine.GUI
{
    /// <summary>
    /// Interaction logic for MeshResolutionViz.xaml
    /// </summary>
    public partial class MeshResolutionViz : UserControl
    {
        // relative size of the nucleus
        private const double innerFraction = 0.5;
        
        public int InnerRadialResolution {
            get { return (int)GetValue(InnerRadialResolutionProperty); }
            set { SetValue(InnerRadialResolutionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InnerRadialResolution.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InnerRadialResolutionProperty =
            DependencyProperty.Register("InnerRadialResolution", typeof(int), typeof(MeshResolutionViz), new PropertyMetadata(1, OnResolutionPropertyChanged));

        public int OuterRadialResolution
        {
            get { return (int)GetValue(OuterRadialResolutionProperty); }
            set { SetValue(OuterRadialResolutionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for OuterRadialResolution.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OuterRadialResolutionProperty = 
            DependencyProperty.Register("OuterRadialResolution", typeof(int), typeof(MeshResolutionViz), new PropertyMetadata(1, OnResolutionPropertyChanged));

        public int CircumferentialResolution {
            get { return (int)GetValue(CircumferentialResolutionProperty); }
            set { SetValue(CircumferentialResolutionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CircumferentialResolution.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CircumferentialResolutionProperty =
            DependencyProperty.Register("CircumferentialResolution", typeof(int), typeof(MeshResolutionViz), new PropertyMetadata(1, OnResolutionPropertyChanged));

        public SolidColorBrush StrokeColor {
            get { return (SolidColorBrush)GetValue(StrokeColorProperty); }
            set { SetValue(StrokeColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StrokeColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StrokeColorProperty =
            DependencyProperty.Register("StrokeColor", typeof(SolidColorBrush), typeof(MeshResolutionViz), new PropertyMetadata(Brushes.DarkGray, OnResolutionPropertyChanged));

        public SolidColorBrush OutlineStrokeColor {
            get { return (SolidColorBrush)GetValue(OutlineStrokeColorProperty); }
            set { SetValue(OutlineStrokeColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for OutlineStrokeColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OutlineStrokeColorProperty =
            DependencyProperty.Register("OutlineStrokeColor", typeof(SolidColorBrush), typeof(MeshResolutionViz), new PropertyMetadata(Brushes.Black, OnResolutionPropertyChanged));

        public double StrokeThickness {
            get { return (double)GetValue(StrokeThicknessProperty); }
            set { SetValue(StrokeThicknessProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StrokeThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StrokeThicknessProperty =
            DependencyProperty.Register("StrokeThickness", typeof(double), typeof(MeshResolutionViz), new PropertyMetadata(2.0, OnResolutionPropertyChanged));

        public double OutlineStrokeThickness {
            get { return (double)GetValue(OutlineStrokeThicknessProperty); }
            set { SetValue(OutlineStrokeThicknessProperty, value); }
        }

        // Using a DependencyProperty as the backing store for OutlineStrokeThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OutlineStrokeThicknessProperty =
            DependencyProperty.Register("OutlineStrokeThickness", typeof(double), typeof(MeshResolutionViz), new PropertyMetadata(3.0, OnResolutionPropertyChanged));

        public static void OnResolutionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) {
            var meshResCtrl = d as MeshResolutionViz;
            //MessageBox.Show(string.Format("new res = {0}", meshResCtrl.InnerRadialResolution));
            if (meshResCtrl != null) {
                meshResCtrl.RedrawMeshViz();
            }
        }

        private double EllipseRadius(double a, double b, double angle) {
            var phi = (angle * Math.PI) / 180.0;
            var sin = Math.Sin(phi);
            var cos = Math.Cos(phi);
            // r(phi) = (a * b) / sqrt(a^2 * sin(phi)^2 + b^2 * cos(phi)^2)
            return (a * b) / Math.Sqrt(a * a * sin * sin + b * b * cos * cos);
        }

        private Line LineFromCenterToEllipse(double a, double b, double centerX, double centerY, double angle) {
            var tg = new TransformGroup();
            tg.Children.Add(new TranslateTransform(centerX, centerY));
            tg.Children.Add(new RotateTransform(angle, centerX, centerY));
            
            return new Line {
                StrokeThickness = StrokeThickness,
                Stroke = StrokeColor,
                X1 = 0.0,
                Y1 = 0.0,
                X2 = EllipseRadius(a, b, angle),
                Y2 = 0.0,
                RenderTransform = tg
            };
        }

        private void RedrawMeshViz() {
            canvas.Children.Clear();

            // vertical extents of the ellipses
            var outerRadiusW = Width / 2.0;
            var innerRadiusW = innerFraction * outerRadiusW;
            // horizontal extents
            var outerRadiusH = Height / 2.0;
            var innerRadiusH = innerFraction * outerRadiusH;

            // center of the canvas
            var centerX = Width / 2.0;
            var centerY = Height / 2.0;

            // CircumferentialResolution is defined per quadrant
            for (int i = 0; i < CircumferentialResolution * 4; ++i) {
                var angle = (360.0 / (CircumferentialResolution * 4)) * i;
                canvas.Children.Add(
                    LineFromCenterToEllipse(outerRadiusW, outerRadiusH, centerX, centerY, angle));
            }

            // test: transform shape/geometry only, but not the brush/stroke:
            //var eg = new EllipseGeometry(new Point(centerX, centerY), outerRadiusW, outerRadiusH, new ScaleTransform(10.0, 1.0));
            //var p = new Path();
            //p.Data = eg;
            //p.Stroke = Brushes.Red;
            //p.StrokeThickness = 3.0;
            //canvas.Children.Add(p);            

            // generate circles representing the mesh above the nucleus
            for (int i = 1; i < InnerRadialResolution; ++i) {
                var radiusW = innerRadiusW * (i / (double)InnerRadialResolution);
                var radiusH = innerRadiusH * (i / (double)InnerRadialResolution);
                canvas.Children.Add(new Ellipse {
                    Width = radiusW * 2.0,
                    Height = radiusH * 2.0,
                    Stroke = StrokeColor,
                    StrokeThickness = StrokeThickness,
                    // center on canvas
                    RenderTransform = new TranslateTransform(outerRadiusW - radiusW, outerRadiusH - radiusH)
                });
            }

            // generate circles representing the mesh above the annulus
            for (int i = 1; i < OuterRadialResolution; ++i) {
                var radiusW = innerRadiusW + (outerRadiusW - innerRadiusW) * (i / (double)OuterRadialResolution);
                var radiusH = innerRadiusH + (outerRadiusH - innerRadiusH) * (i / (double)OuterRadialResolution);
                canvas.Children.Add(new Ellipse {
                    Width = radiusW * 2.0,
                    Height = radiusH * 2.0,
                    Stroke = StrokeColor,
                    StrokeThickness = StrokeThickness,
                    // center on canvas
                    RenderTransform = new TranslateTransform(outerRadiusW - radiusW, outerRadiusH - radiusH)
                });
            }
            
            // outline the boundary of the VB
            canvas.Children.Add(new Ellipse {
                Width = outerRadiusW * 2.0,
                Height = outerRadiusH * 2.0,
                Stroke = OutlineStrokeColor,
                StrokeThickness = OutlineStrokeThickness
            });

            // outline the boundary between nucleus and annulus
            canvas.Children.Add(new Ellipse {
                Width = innerRadiusW * 2.0,
                Height = innerRadiusH * 2.0,
                Stroke = OutlineStrokeColor,
                StrokeThickness = OutlineStrokeThickness,
                // center on canvas
                RenderTransform = new TranslateTransform(outerRadiusW - innerRadiusW, outerRadiusH - innerRadiusH)
            });            
        }

        public MeshResolutionViz() {
            InitializeComponent();
        }
    }
}
